#====================================================================== QfPreProcess.py =#
# QfPreProcess.py	Version 1.0															 #
# Original author: John Pote  8 July 2013												 #
#																						 #
#	Copyright (c) 2013, John Pote 														 #
#	All rights reserved. 																 #
#																						 #
#	Redistribution and use in source and binary forms, with or without					 #
#	modification, are permitted provided that the following conditions are met:			 #
#																						 #
#	 * Redistributions of source code must retain the above copyright notice,			 #
#	   this list of conditions and the following disclaimer.							 #
#	 * Redistributions in binary form must reproduce the above copyright				 #
#	   notice, this list of conditions and the following disclaimer in the				 #
#	   documentation and/or other materials provided with the distribution.				 #
#	 * Neither the name of JP Technical Services Ltd nor the names of its				 #
#	   contributors may be used to endorse or promote products derived from				 #
#	   this software without specific prior written permission.							 #
#																						 #
#	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"			 #
#	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE			 #
#	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE			 #
#	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE			 #
#	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR					 #
#	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF				 #
#	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS			 #
#	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN				 #
#	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)				 #
#	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE			 #
#	POSSIBILITY OF SUCH DAMAGE.															 #
#																						 #
#========================================================================================#
ver = 1.0

## @mainpage
#	This module is a pre-processor for a CCS 'C' compiler for PIC 18Fxxx processors.
#	It translates #rom statements to the format used by the 'C' compiler. In the process
#	it calculates the size and location of each constant object to be placed in the
#	processor's flash ROM. All such objects are placed in one contiguous block.
#
#	Usage: QfPreProcessor.py [-o dstFile] [-tkns] [-debug] srcFile
#		   QfPreProcessor.py ?|/h|-h|--help
#		   					-tkns     emit list of tokens from src file to dstFile.tkn
#		   					-debug    emit debug info to  dstFile.dbg
#
#	Default dstFile extension is '.c'. Tokens and debug files are '.tkns' and '.dbg'.
#
#	srcFile format is:-
#
#		#rom	ID, int8 { int8, int8 ... }
#		#rom	ID, int16 { int16, int16 ... }
#		#rom	ID, "Quoted text string"
#		#rom	ID, """
#		Block text a
#		 la Python
#		"""
#		#rom	(locateLo|locateHi)@addr;
#
#		ID - name of rom block.
#		locateLo|locateHi
#			literals specifying whether addr is the beginning or ending
#			address of all #rom statements. Since the CCS compiler requires its #rom statements
#			to specify an absolute address addr must be a constant number as the pre-processor
#			does not do expression evaluation.
#		addr - location of rom block in decimal or 0x hex.
#		Python style block strings may us the common '\' '\n' sequence to extend a line on the
#		following line. Otherwise new lines in the block string are retained as '\r\n'
#		sequences.


import os.path, re, time
from sys import *
from numbers import Number
import ply.yacc as yacc


opFileExt  = ".c"
tknFileExt = ".tkns"
dbgFileExt = ".dbg"
dstLineLen = 90			#default -l option value
lineWhtSpc = "    "		#Initial white space on rom stmt data lines in dst file

## Destination file header text.
#	Copied to the destination file with various formatting macros filled in. It includes a
#	copyright notice that should be modified to suit the user.
opPreamble = """\
/*{fileNameFill} {fileName} */

/*****************************************************************************************
*	Copyright (c) 2013, John Pote 														 *
*	All rights reserved. 																 *
* The copyright in this document is the property of JP Technical Services Ltd.			 *
* The document is supplied by JP Technical Services on the express understanding that	 *
* it is to be treated as confidential and that it may not be copied, used or disclosed	 *
* to others in whole or in part for any purpose except as authorised in writing by		 *
* JP Technical Services Ltd.															 *
******************************************************************************************
                                    !!!!!!! N B !!!!!!!
  This file is automatically generated. It will be overwritten next time the system is
  built.

  Created by {application} ver {verString} from file '{srcFile}'.
  on {date} at {time}.
  Description : See original source file above.
*****************************************************************************************/


"""

	
def enum(*sequential):
	enums = dict(zip(sequential, range(len(sequential))))
	enums['inverse'] = dict( (val,key) for key, val in enums.iteritems())
	return type('Enum', (), enums)

LocateEnum   = enum("hi", "low")
DataTypeEnum = enum( "int8", "int16", "string",		#types for #rom statements
					 "text"							#for all other text
					 )

tknFile = None		#place holder for tokens file


##	Handler to collect all #rom statement data, calculate sizes and locations and output
#	to dstFile.
#	Data from each #rom statement is held in dictionary with the following elements,
#		'blkName'		Name of the flash ROM block defined by the #rom statement
#		'dataType'		Data type from DataTypeEnum
#		'data'			List of strings, each string is a data element
#		'lineno'		Line no. of #rom token
class RomHandler:
	## Constructor.
	#
	#	@param	opFile		Destination for translated source. An open file obj.
	#	@param	dbgFile		An open file obj for debug output. Defaults to 'None'.
	#
	def __init__( self, opFile, dbgFile=None ):
		self.locationCnt = 0		#0 based byte count for rom statements
		self.romObjs = []			#list of rom objects, one for each #rom statement
		self.locateAt = None		#must be assigned hi or lo for op file to be created
		self.locateAddr = None		#high or low location address in rom (1st or last
									#byte of data)
		self.opFile = opFile
		self.debugFile = dbgFile	#An op file for emitting debug to

	def debugDump( self ):
		dbg = self.debugFile			#local for debug op 
		dbg.write( "\n\nDebug dump of #rom statements to be translated\n")
		dbg.write( "Processed #rom statements output to '%s'\n\n" % opFile.name )
		dbg.write( "Locate info has not been set\n" if self.locateAt == None else
				   "Locate %s @ 0x%04x\n" % (LocateEnum.inverse[self.locateAt],
											 self.locateAddr
											 )
				   )
		for obj in self.romObjs:		#obj is a dictionary
			txt = "Rom block '%s' type '%s'\n" % (obj['blkName'],
												  DataTypeEnum.inverse[obj['dataType']])
			dbg.write( txt )
			if obj['dataType']==DataTypeEnum.string:
				dbg.write( obj['data'][0] )
			else:
				for elm in obj['data']:
					dbg.write( "\t%s\n" % elm )
			dbg.write( '\n\n' )

	# Param mode should be from LocationEnum
	def setLocation( self, lineno, mode, addr, src ):
		self.locateLnNo = lineno
		self.locateAt   = mode
		self.locateAddr = addr
		self.locateSrc  = src

	##	Append a ROM data block to data block list.
	#	@param 'data' - see class notes
	def dataBlk( self, data ):
		self.romObjs.append( data )

	##	Process all the #rom statements form the source file.
	#	Data from each #rom statement is in a dictionary in the list of dictionaries
	#	'romObjs'.
	#	First calculate the size of each romObjs[], total size and start address of the
	#	first object. Then emit a #rom statement in the CCS compiler format and a
	#	corresponding #define for the address of the ROM block that can be used in the
	#	rest of the program to refer to that particular ROM object.
	#	Emit results to 'self.opFile'.
	def processSrc( self ):
		#Calculate start address
		#for each #rom stmt cal size, add to stmt dictionary. Inc total size.
		totalSz = 0			#Total size of #rom stmts in bytes
		objCnt = 0			#Index into self.romObjs
		for obj in self.romObjs:
			if obj['dataType'] == DataTypeEnum.text:
				#text blocks do not contribute to #rom data
				#calulate size for debug purposes
				obj['size'] = len( obj['data'][0] )
			else:		# #rom statement data
				size = len( obj['data'] )
				if obj['dataType'] == DataTypeEnum.int8:
					pass				#1 byte per element in a rom block
				elif obj['dataType'] == DataTypeEnum.int16:
					size *= 2			#each element in rom block is 16 bits (2 bytes)
				else:		#must be a string
					size = len( obj['data'][0] )	#1 byte per char
				size = (size + 1 ) & ~1				#round to next higher even value
				obj['size'] = size
				obj['relAddr'] = totalSz			#rom statement addr form base 0
				totalSz += size
			objCnt += 1
		#cal start addr from location type and addr
		if self.locateAt == LocateEnum.low:
			self.startAddr = self.locateAddr	#location address is the start address
		else:		#must be LocateEnum.hi
			self.startAddr = self.locateAddr + 1 - totalSz

		#emit preamble
		tm = time.localtime()
		self.opFile.write( opPreamble.format(
			fileNameFill = '*'*(dstLineLen - len(dstFileNm) - 6),
			fileName = dstFileNm,
			application = os.path.basename( argv[0] ),
			verString = str( ver ),
			srcFile = srcFileNm,
			date = time.strftime("%d/%m/%Y",tm),
			time = time.strftime("%X",tm)
			)
		)

		if emitDebug:
			#emit location mode and address
			self.opFile.write( """\
/* Rom statements from src file are located %s in flash ROM at %04x */\n""" % (
				LocateEnum.inverse[self.locateAt], self.locateAddr
				)
			)
			#emit rom stmt for each src stmt
			for romStmt in self.romObjs:
				if romStmt['dataType'] == DataTypeEnum.text:
					self.opFile.write( "%s (%d): %s\n\n" % ( romStmt['blkName'],
						romStmt['size'], romStmt['data']
						)
					)
				else:
					self.opFile.write( "%s (%d) @ %04x: %s\n\n" % ( romStmt['blkName'],
						romStmt['size'], romStmt['relAddr'], romStmt['data']
						)
					)

		if self.locateAt == LocateEnum.low:
			romAddr = self.locateAddr
		else:
			romAddr = self.locateAddr + 1 - totalSz

		self.opFile.write( "/* Line %d: %s */\n\n" % (self.locateLnNo, self.locateSrc) )

		for romStmt in self.romObjs:
			dataType = romStmt['dataType']
			if dataType == DataTypeEnum.text:
				self.opFile.write( romStmt['data'][0] )

			else:	# #rom statement dataType
				self.opFile.write( "/* Line %d */\n" % romStmt['lineno'] )
				name = romStmt['blkName']
				self.opFile.write( "#define %s\t0x%04x\n" % (name, romAddr) )
				romStmtStart = "#rom    %s = { " % name
				linePosn = len( romStmtStart )
				self.opFile.write( romStmtStart )
				
				#build up elements in list of strings
				elmList = []
				elm1 = ''		#Place holder to combine first of 2 byte elements into 1
								#16 bit word.
				for elm in romStmt['data']:
					if dataType == DataTypeEnum.int8:
						if not elm1:
							elm1 = elm
						else:	#have prior element
							elmList.append( '%s + ((%s)<<8),' % (elm1, elm) )
							elm1 = ''
	
					elif dataType == DataTypeEnum.int16:
						elmList.append( '%s,' % elm )
					else:	#DataTypeEnum.string
						elmList.append( '"'+elm+'",' )
				if elm1:
					elmList.append( '%s,' % elm1 )
				#remove ',' from last element
				elmList[-1] = elmList[-1][:-1]
	
				#emit element list, can it be placed on one line?
				stmtTerminator = "}\n\n"
				totalElmsSz = 0
				for elm in elmList: totalElmsSz += len(elm) + 2 	#+2 for ', ' separator
				if linePosn + totalElmsSz > dstLineLen:
					#too big for one line, go to next line
					self.opFile.write( "\n" + lineWhtSpc )
					linePosn = len( lineWhtSpc )
					multiLn = True
				else:
					multiLn = False
				elmSpc = ''		#inter element space
				for elm in elmList:
					if linePosn + len(elm) < dstLineLen:
						#will fit on current line
						elmOpStr = elmSpc + elm
					else:
						#go to new line first
						elmOpStr = lineWhtSpc + elm
						self.opFile.write( '\n' )
						linePosn = 0
					elmSpc = ' '
					self.opFile.write( elmOpStr )
					linePosn += len( elmOpStr )
	
				self.opFile.write( ' ' if not multiLn else '\n' )
				self.opFile.write( stmtTerminator )
				romAddr += romStmt['size']		#addr of following #rom statement

		self.opFile.write( "\n/* Total size (bytes): %d (0x%04x) */\n\n"
						  % (totalSz,totalSz) )





#-----------------------------------------------------------------------------------------
#
# Parse the command line
#
#-----------------------------------------------------------------------------------------

class ArgvErr(StandardError):
	"""Command line argument error"""
	def __init__( self, str, exitCode=1 ):
		self.args = ( str, exitCode )

dbgFile = None			#place holder for debug file

argvLen = len(argv)
try:
	srcFileNm  = None
	dstCmdLnNm = None
	if argvLen<2 or argv[1] in ('?','/h','-h','--help'):
		raise ArgvErr( """\
Usage: QfPreProcessor.py [-o dstFile] [-tkns] [-debug] srcFile
       QfPreProcessor.py ?|/h|-h|--help
           -tkns     emit list of tokens from src file to dstFile.tkn
           -debug    emit debug info to  dstFile.dbg
""",
		0
		)
	else:
		#default option values
		emitTkns  = False
		emitDebug = False
		#scan command line args
		arg = 1						#1st arg after script name
		while arg < argvLen:
			if argv[arg][0] == '-':#process options
				if argv[arg]=='-o':
					arg += 1			#dst file name should follow
					if arg >= argvLen:
						raise ArgvErr( "no dst file name" )
					dstCmdLnNm = argv[arg]
					arg += 1			#skip over the file name
				elif argv[arg]=='-tkns':
					emitTkns = True
					arg += 1			#skip over option
				elif argv[arg]=='-debug':
					emitDebug = True
					arg += 1			#skip over option
				else:
					raise ArgvErr( "option %s not supported"%argv[arg] )
			else:
				srcFileNm = argv[arg]	#get input file name
				arg += 1

		#open files, if dst file name not given make one from source name
		srcHd,srcTail = os.path.split( srcFileNm )
		srcNm = srcTail						#name of src file without extension
		srcExtIndx = srcTail.rfind('.')
		if srcExtIndx != -1:
			srcNm = srcTail[:srcExtIndx]

		if dstCmdLnNm != None:
			dstFileNmRoot = dstCmdLnNm		#use name from cmd line as is
			dstExtIndx = dstCmdLnNm.rfind('.')
		else:							#get dst file name from src file name
			dstFileNmRoot = os.path.join( srcHd, srcNm )

		dstFileNm = dstFileNmRoot
		if dstCmdLnNm == None or dstCmdLnNm != None and dstExtIndx == -1:
			dstFileNm += opFileExt
		if 	emitDebug == True:
			dbgFileNm = dstFileNmRoot + dbgFileExt
			dbgFile = open( dbgFileNm,'w' )

		srcFile = open(srcFileNm,'r')	#will be read by the scanner
		#asmOutput = AssemblerOutput(dstFileNm,opLineLen,asmCommentDelim)


except ArgvErr, e:
	print e.args[0]		#print the error str
	exit( e.args[1] )	#exit with specified error code
except IOError, e:
	print str(e)
	exit(1)


print "Converting: '%s' to '%s'" % (srcFileNm,dstFileNm)



#-----------------------------------------------------------------------------------------
#
# Now follows the PLY package lex/yacc style parser generator
#
#-----------------------------------------------------------------------------------------


errorCount = 0		#count of all errors found by scanner and parser
def printErr(str):
	global errorCount

	if emitTkns == True:
		tknFile.write( str )
	else:
		if emitDebug == True:
			dbgFile.write( str )
	print(str)
	errorCount += 1


#-----------------------------------------------------------------------------------------
# create the input scanner (or lexer)
#-----------------------------------------------------------------------------------------
import ply.lex as lex

#elements of a token (tuple indices)
TKNTYPE   = 0
TKNVALUE  = 1
TKNLINENO = 2
TKNLINEPOSN = 3

# List of token names. Always required.
tokens = [
	'HASHROM',
	'OPERATOR',
	'NUMBER',
	'LITERAL',
	'ID',
	'STRING',
	'BLOCKSTRING',
	'CCODE'
]

# Regular expression rules for simple tokens
t_HASHROM	= r'\#rom'
t_OPERATOR  = r'[+\-*/]+'	#defines the chars that are operators within an expression
t_LITERAL   = r"'(.|0x[0-9A-Za-z]{2})'"
literals = "{}()@,;"	#list of permitted literals, others will raise an error

t_ignore  = ' \t'	#these chars are ignored

reservedWords = {
	'int8':		'INT8',
	'int16':	'INT16',
	'locateLo': 'LOCATE_LO',
	'locateHi': 'LOCATE_HI'
}

tokens = tokens + list(reservedWords.values())#add reserved word list to tokens tuple



#-----------------------------------------------------------------------------------------
# Lexer code for text between #rom statements. This text is not modified as it is passed
# to the 'destFile'.
#-----------------------------------------------------------------------------------------

##	Lexer states.
#	Use lexer states to switch between #rom statement tokenising and 'other' code
#	tokenising. Want to be able to pass through all code between #rom statements 'as is'.
#
#	'ccode' is the state for all non-#rom 'C' code. It is exclusive and picks up all text
#	up to the next #rom statement or end of file.
#	There is no explicit state for the #rom statements as these are all scanned in the
#	default 'INITIAL' state. 
states = (
  ('ccode','exclusive'),	#this state is for all non-#rom code
)

#Non-#rom code tokens
def t_ccode_CCODE( t ):
	r'(?s).+?((?=\#rom)|\Z)'	#match everything up to but not including #rom or EOT
								#includes \n's hence dot all option.
	t.lexer.lineno += t.value.count('\n')	#keep track of line numbers.
	return t

t_ccode_ignore  = ''	#no chars to ignor in state CCODE





#-----------------------------------------------------------------------------------------
# Lexer code for #rom statements. 
#-----------------------------------------------------------------------------------------


##	Comments, which may span several lines, embedded in #rom statements are ignored.
#	Nested comments are not handled.
def t_COMMENT( t ):
	r'(?s)/\*.*?\*/'	#note use of dot all so comments can span many lines. It is
						#lazy so only one comment is matched.
	t.lexer.lineno += t.value.count('\n')	#track line numbers through multi-line
											#comments.

	#comments are not returned to the parser, ie dumped, so no return here.

##	#rom statements may include numbers. May be hex (0x...) or decimal.
def t_NUMBER( t ):
	r'0x[0-9A-Fa-f]+|\d+'
	return t

##	#rom statements may include any of the reserved words or any other identifier.
#	The usual what's in a name, underscore, lower an dupper case letters and decimal
#	digits. The first character must be a letter or underscore.
def t_ID( t ):
	r'[a-zA-Z_][a-zA-Z_0-9]*'
	t.type = reservedWords.get(t.value,'ID')
						# Check for reserved words, otherwise return 'ID' type
	return t



#Escape seq translation is redundant, to be removed. Only need to translate non-printable
#chars.
translateTbl = {
	#non-printable chars
	'\n': r"\r\n", '\r': r"\r", '\t': r"\t",
	}
# Param txt	- text to re-format. Still includes quote chars (") from source file.
# Param nQuoteChars	- Num of " chars at each end of txt
def formatString( txt, nQuoteChars ):
	#Strip 'nQuoteChars' quote chars at each end of string. Join lines separated by a
	#'\' '\n' seq.
	txt =  txt[nQuoteChars:-nQuoteChars].replace( '\\\n', '' )
	
	#Convert Non-printable chars to esc sequences
	opList = []		#list of text sections separated by ctrl chars from 'txt'.
	prev_i = 0		#index into 'txt' of current normal text not yet appended to opList[].
	for i in range( len(txt) ):
		ch = txt[i]
		if ord(ch) < 0x20:
			opList.append(''.join(txt[prev_i:i]))	#copy remaining prev text to opList
			opList.append( translateTbl[ch] )		#non-printable char translates
			prev_i = i + 1	#skip past just translated char
	opList.append(''.join(txt[prev_i:len(txt)]))

	return ''.join( opList )

##	Multi-line block string a la Python.
#	All text between pairs of triple quotes - """....""".
def t_BLOCKSTRING( t ):
	r'""".*?"""'	#force lazy matching so only 1 block is matched.
	t.lexer.lineno += t.value.count('\n')
	t.value = formatString( t.value, 3 )
	return t

##	Normal string enclosed by double quote chars.
#	Handles embedded \" escape sequences. Tracks line numbers through strings that may be
#	spread over several lines.
def t_STRING( t ):
	r'".*?(?<!\\)"'		#not very nice but prevents '\"' sequences terminating a string
						#ie allows escaped '"'s to be included in a string.
	t.lexer.lineno += t.value.count('\n')
	t.value = formatString( t.value, 1 )
	return t

## Newline token.
#	Specific rule for line numbers mainly so line numbers can be counted. They are not
#	returned to the parser but dumped.
def t_newline(t):
	r'\n+'
	t.lexer.lineno += len(t.value)	#inc lineno by number of '\n's


## Error handling for default INITIAL state.
def t_error( t ):
	tErrorHandler( t )

## Error handling for CCODE state.
def t_ccode_error( t ):
	tErrorHandler( t )

def tErrorHandler( t ):
	printErr( "Line %d: Illegal character '%s'\n" % (t.lexer.lineno,t.value.strip()) )
	t.lexer.skip(1)



scanner = lex.lex()			# Build the lexer
scanner.begin( "ccode" )	#Initial state. Assume there might be non-#rom text at
							#beginning of file.
scanner.input( srcFile.read() )

#Test mode. Dump all tokens to '.tkns. file and terminate.
if emitTkns:
	tknsFileNm = dstFileNmRoot + tknFileExt
	print "Creating tokens file '%s'\n" % tknsFileNm
	tknFile = open( tknsFileNm, 'w' )

	TKNSTATES = enum( "CCODE", "rom", "romList" )
	tknState = TKNSTATES.CCODE

	tok = scanner.token()
	romFinished = False
	while tok:
		v,t = (tok.value,tok.type)
		if v.find('\n') != -1 or len(v)>90:	#value contains \n's or long
			tknFile.write( t + ':\n' + v + '$$$\n' )
		else:	#short 1 line value
			tknFile.write( t + ': ' + v + '$$$\n' )

		if tok.type == "CCODE":
			tknState = TKNSTATES.rom
			scanner.begin( "INITIAL" )

		elif tknState == TKNSTATES.rom:
			if tok.value == '{':
				tknState = TKNSTATES.romList
			elif tok.type in ("STRING","BLOCKSTRING",";"):
				romFinished = True
			else:
				pass

		elif tknState == TKNSTATES.romList:
			if tok.value == '}':
				romFinished = True
			else:
				pass

		if romFinished:
			tknState = TKNSTATES.CCODE
			romFinished = False
			scanner.begin( "ccode" )
		tok = scanner.token()
	exit()


opFile = open( dstFileNm, "w" )
romHandler = RomHandler( opFile, dbgFile=dbgFile )		#singleton for use by the parser


#-----------------------------------------------------------------------------------------
# Create the parser
#-----------------------------------------------------------------------------------------

def p_root(p):
	'root : statements'
	pass

def p_statements(p):
	"""statements : statement
				  | statements statement
	"""
	pass



#-----------------------------------------------------------------------------------------
# Productions handling blocks of CCODE text.
#-----------------------------------------------------------------------------------------

#Catch all statement for all text in CCODE tokens. To be passed on as is to the dst file.
#Since a #rom statement or EOT must follow the lexer is switched to the default state
#'INITIAL' by the 'haveCCODE' empty production.
def p_statementCCODE(p):
	"""statement : haveCCODE CCODE"""
	#Pass text block to the output handler. Text blocks are given a generic 'blkName'.
	romHandler.dataBlk( { 'blkName':   'text',
						  'dataType':  DataTypeEnum.text,
						  'data':      [p[2]],
						  'lineno':    p.lineno(2)
					} )

#Empty production executes when the lookahead token is type CCODE and the lexer must
#switch states to the #rom state.
def p_haveCCODE(p):
	"""haveCCODE : """
	p.lexer.begin('INITIAL')	# Switch to default #rom statement state




#-----------------------------------------------------------------------------------------
# Productions handling non-CCODE statements.
#-----------------------------------------------------------------------------------------

## Place to group all non-CCODE statements. For future expansion.
def p_statementROM(p):
	"""statement : romStmnt"""

LOCPARAMS  = 0		#for location statement
DATAPARAMS = 1		#for all other #rom statements
## Production for all HASHROM statements.
def p_romStmnt(p):
	"""romStmnt : HASHROM romParams"""
	if p[2][0] == LOCPARAMS:
		src = p[1]+' '+p[2][1][2]		#orig src code line
		p[2][1][2] = src
		romHandler.setLocation( p.lineno(1), *p[2][1] )
		#For completeness copy the src line to dst file as a comment.
		romHandler.dataBlk( { 'blkName':  'text',
							  'dataType': DataTypeEnum.text,
							  'data':     ["/* %s */\n" % src],	#put src line in comment
							  'lineno':   p.lineno(2)
						} )
	else:		#DATAPARAMS
		data = p[2][1]
		data['lineno'] = p.lineno(1)
		romHandler.dataBlk( data )



##	Empty production to switch lexer to 'ccode' state.
#	This production must be placed so it is reduced, ie executed, when the lookahead token
#	is the final token of the statement.
def p_romStmtEnd(p):
	"""romStmtEnd : """
	p.lexer.begin( 'ccode' )	# Switch to ccode text state




## Production for #rom locate statement.
def p_romLocParams(p):
	"""romParams : LOCATE_LO '@' expr romStmtEnd ';'
				 | LOCATE_HI '@' expr romStmtEnd ';'
	"""
	p[0] = ( LOCPARAMS,
			[	LocateEnum.hi if reservedWords[p[1]]=='LOCATE_HI' else LocateEnum.low,
				int(p[3], 0),				#location addr, any base
				p[1]+' '+p[2]+' '+p[3]		#orig source
				]
			)


## Production for normal #rom statements.
def p_romDefineParams(p):
	"""romParams :  ID ',' romStmntData"""
	p[3]['blkName'] = p[1]		#add rom stmt name to dict
	p[0] = ( DATAPARAMS, p[3] )




def p_romListData(p):
	"""romStmntData : romListType '{' exprList  romStmtEnd '}'"""
	p[0] = { 'dataType': p[1], 'data': p[3] }

def p_romStrData(p):
	"""romStmntData : romStmtEnd romStr"""
	p[0] = { 'dataType': DataTypeEnum.string, 'data': [p[2]] }


def p_romStr(p):
	"""romStr : STRING
			  | BLOCKSTRING
	"""
	p[0] = p[1]


def p_romListType8(p):
	"""romListType : INT8"""
	p[0] = DataTypeEnum.int8

def p_romListType16(p):
	"""romListType : INT16"""
	p[0] = DataTypeEnum.int16


#-----------------------------------------------------------------------------------------
# Productions for expressions. Expression text is simply collected together as a string
# for sending as is (ie not evaluated) to the destination file. The 'C' compiler will
# evaluate expressions as it needs to.
#-----------------------------------------------------------------------------------------
def p_exprList1(p):
	"""exprList : expr"""
	p[0] = [ p[1] ]		#put expr into list, 1st element

def p_exprList2(p):
	"""exprList : exprList ',' expr"""
	p[1].append( p[3] )
	p[0] = p[1]			#retn the list of elements


#for a single element expr return the value as is so ints remain ints
def p_expr1(p):
	"""expr : NUMBER
			| ID
			| LITERAL
	"""
	p[0] = p[1]

def p_expr2(p):
	"""expr : OPERATOR expr"""
	p[0] = p[1] + p[2]

def p_expr3(p):
	"""expr : expr OPERATOR expr
	        | '(' expr ')'
	"""
	p[0] = p[1] + p[2] + p[3]




## Catchall rule for syntax errors
def p_error(t):
	if t==None:
		printErr( "Unexpected end of file" )
	else:
		printErr( "Line %d: Syntax error at symbol '%s'\n"%(t.lineno,t.value) )
		






# Build the parser
parser = yacc.yacc()

if emitDebug:
	lexTokenOrig = scanner.token
	
	def tokenHandler():
		tkn = lexTokenOrig()
		print "next token: %s\n" % tkn
		return tkn
	
	scanner.token = tokenHandler



parser.parse( lexer=scanner, debug=1 if emitDebug==True else 0 )

if errorCount == 0:
	romHandler.processSrc()
	print "Done"
else:
	if errorCount == 1: errorS = ''
	else:				errorS = 's'
	print "%d error%s found in '%s'"%(errorCount, errorS, srcFileNm)

opFile.close()
if emitDebug:
	romHandler.debugDump()
	dbgFile.close()

